package gbench.appdemo.priceline.app;


import java.time.LocalDate;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import gbench.common.tree.LittleTree.IRecord;
import gbench.appdemo.priceline.calendar.TradingCalendar;
import static gbench.common.tree.LittleTree.MFT;
import static gbench.common.tree.LittleTree.CronTime.ld2dt;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.appdemo.priceline.calendar.TradingCalendar.PERIOD_UNIT_DAY;
import static gbench.appdemo.priceline.calendar.TradingCalendar.PERIOD_UNIT_MONTH;
import static gbench.appdemo.priceline.calendar.TradingCalendar.PERIOD_UNIT_WEEK;
import static gbench.appdemo.priceline.calendar.TradingCalendar.PERIOD_UNIT_YEAR;

/**
 * 
 * @author gbench
 *
 */
@RequestMapping("/finance/kline")
@RestController
@SpringBootApplication 
public class PriceLineBootstrap {
    
    /**
     * started callback
     * @return ApplicationRunner
     */
    @Bean
    ApplicationRunner onStarted() {
        return args->{
            priceLineApp.initialize(pla -> {
                final var dataApp = pla.new ShfexDataApp();// 注册数据源
                final var period = pla.getPeriod();// 时间周期
                final var cld = pla.getCalendar();// 日历时间
                final var begin = ld2dt(LocalDate.of(2020, 01, 01));// 开始时点
                final var end = ld2dt(LocalDate.of(2021, 01, 01)); // 结束时点
                
                final var future_sql = MFT(
                    "SELECT ticker,time,price,volume FROM ohlc0310.t_deriv_ma005 "
                        + " where create_time >''{0,date,yyyy-MM-dd HH:mm:ss}''"
                        //+ " limit 1000"
                    ,begin
                );// 现货sql
                
                final var underlying_sql = MFT(
                    "select price,create_at time from salepro.offer_price_log "
                        + " where sys_id={0} and offer_id={1} "
                        //+ " and create_at between ''{2,date,yyyy-MM-dd HH:mm:ss}'' and ''{3,date,yyyy-MM-dd HH:mm:ss}''"
                        +"order by id desc limit 1" // 提取最新数据
                    ,2,104,begin,end
                ); // 期货sql
                
                final var underlying_rec = dataApp.sqlOne(underlying_sql);
                final var underlying_price = underlying_rec.dbl("price");
                
                // 创建一个数据应用
                final var dataApplet = dataApp.getApplet(line->{
                    line
                        .add("index",cld.getIndex(line.ldt("time"), period))
                        .compute("price",(Double price)->price-underlying_price);
                    return line;
                },future_sql);// 数据应用程序
                
                return dataApplet;
            });// initialize
        };
    }
    
    /**
     * 价格线数据
     * 
     * @param symbol 合约符号
     * @param range 合约周期
     * @param from 开始索引包含，从0开始
     * @param to 结束索引  不包含。
     * @return IRecord 结果记录。
     */
    @RequestMapping("/priceline3")
    public IRecord priceline3(String symbol,String range,Long from,Long to) {
        
        final Long start = from==null ? 0L: from;// 第一项目的开始偏移
        final var period_millis = period2lng(range);//周期的毫秒时长
        Stream<IRecord> priceline = null;
        priceline = priceLineApp.getPriceLine(start);// 获取价值线数据
        final var dataApp = priceLineApp.new ShfexDataApp();
        var sql = MFT("SELECT * FROM ohlc0310.t_price_line_yichemderiv_2_ma_104_001 where period={0,number,#} order by time"
            , TradingCalendar.PERIOD_UNIT_15MIN);
        
        sql = MFT("SELECT * FROM myexchange.t_price_line_ma001 where period={0,number,#} order by time",period_millis);
        final var final_sql = MFT(
            "SELECT * FROM (\n" +
                "\t SELECT \n"+
                        "\t\t(@rownumber := @rownumber + 1) AS rownum, \n"+
                        "\t\tt.* \n"+
                "\t FROM \n"+
                    "\t\t({0}) t, \n" + // 核心sql
                    "\t\t(SELECT @rownumber := 0) tr \n" + 
            ") tbl WHERE rownum>={1,number,#} \n",
            sql,start);// final_sql
        
        System.out.println(final_sql);
        priceline = dataApp.sqlQuery(final_sql).stream();
        
        //打包成json格式的对象
        return this.priceline2json(start, priceline);
    }
    
    /**
     * 打包成json格式的对象
     * @param start
     * @param priceline
     * @return Json 格式的对象
     */
    public IRecord priceline2json(final Long start,final Stream<IRecord> priceline) {
     // 数据返回
        return REC("success",true, /*接口执行标记*/"data",REC(
            "depths",null,// 深度行情:为了提高效率给予取消
            /**
             * kpoint行项目的数组项字段说明：
             * [0]kpoint时间:time,[1]kpoint开盘价:open,[2]kpoint最高价:high,[3]kpoint最低价:low,
             * [4]kpoint收盘价:close,[5]kpoint量能:volume,[6]kpoint索引:index,[7]kpoint结算价:settle,
             * [8]kpoint前日结算价:presettle。
             */
            "lines",priceline.map(e->Arrays.asList(e.date("time").getTime(),
                e.dbl("open"),e.dbl("high"),e.dbl("low"),e.dbl("close"),e.lng("volume"),
                e.lng("index"),start,e.dbl("settle",0d),e.dbl("presettle",0d))),// 价格数据
            "trades", null,//交易数据：盘口状况:为了提高效率给予取消
            "messages",null // 通知消息
        ));// 外层包裹
    }
    
    /**
     * 把周期名称转换成周期时长:精确到毫秒
     * 5m 转换成 5*60*1000
     * 5h  转换成 5*60*60*1000
     * 5d  转换成 5*24*60*60*1000
     * 其中y,M,w 数值数值只是一个标记并非绝对时间长度：英文他们没有绝对时间长度．
     * @param periodName 周期名,s秒,m分,h时,M月,d天,w周,y 年．
     * @return 周期时长
     */
    public static long period2lng(String period) {
        Matcher m = digits_unit.matcher(period);// 数字加单位匹配器
        long duration =-1;// 周期的时长度
        if(m.matches()) {
            var num = Integer.parseInt(m.group(1));//第一项是基本数字
            var unit = m.group(2);//第二项是单位
            duration = num;
            switch(unit) {
                case "s":{duration = duration*1000;break;}// 分钟
                case "m":{duration = duration*60*1000;break;}// 分钟
                case "h":{duration = duration*60*60*1000;    break;}// 分钟
                case "M":{duration = duration*PERIOD_UNIT_MONTH;break;}// 月份30*24*60*60*1000
                case "d":{duration = duration*PERIOD_UNIT_DAY;break;}//日 24*60*60*1000
                case "w":{duration = duration*PERIOD_UNIT_WEEK;break;}//周7*24*60*60*1000
                case "y":{duration = duration*PERIOD_UNIT_YEAR;break;}//年365*24*60*60*1000
                default :{duration = duration*1000;}//秒:默认项
            }// switch
        }else{
            Matcher mt = digits.matcher(period);//数字匹配器
            if(mt.matches())return Long.parseLong(mt.group(1));//数字匹配器
        }// if match
        
        return duration;
    }
    
    private PriceLineApplication priceLineApp = new PriceLineApplication(
        "MA001",// 合约代码
        TradingCalendar.PERIOD_UNIT_MIN, // 周期间隔
        System.out::println // 回调函数
    );// 应用程序内核
    public static final Pattern digits_unit = Pattern.compile("\\s*(\\d+)\\s*([yMwdhms])\\s*");//数字加单位
    public static final Pattern digits = Pattern.compile("\\s*(\\d+)\\s*");// 纯数字

    /**
     * 启动函数
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(PriceLineBootstrap.class, args);
    }
}
